# -*- coding: utf-8 -*-
import time, datetime

class ResultDocument(object):
    def __init__(self, attrs=None, snippet=None):
        self.__attrs = attrs
        self.__snippet = snippet
        self.__score = None
        self.__attrs_c = None
        self.__kwds_c = None

    def get_rawdata(self):
        return '\n'.join(self.__attrs) + '\n\n' + '\n'.join(self.__snippet)
    rawdata = property(get_rawdata)

    def get_uri(self):
        return self.attrs['uri']
    uri = property(get_uri, doc='The URI of the document.')

    def get_id(self):
        return self.attrs['id']
    id = property(get_id, doc='The ID of the document.')

    def _parse_attrs(self):
        kwds = {}
        attrs = {}
        for line in self.__attrs:
            if line[0] == '@':
                y = line.split('=', 1)
                key = y[0][1:]
                value = y[1]
                
                if key == 'mdate' or key == 'cdate' or key == 'adate':
                    # TODO
                    # Does hype always return dates in this format?
                    attrs[key] = datetime.datetime(*time.strptime(value, '%Y-%m-%dT%H:%M:%SZ')[:6])
                elif key == 'size' or key == 'weight':
                    attrs[key] = long(value)
                else:
                    attrs[key] = unicode(value, 'utf-8')
            elif line[0] == '%':
                if line.startswith('%VECTOR'):
                    tmp = line[8:].split('\t')
                    kwds = dict([(unicode(tmp[i], 'utf-8'), int(tmp[i+1])) for i in xrange(0, len(tmp), 2)])
                elif line.startswith('%SCORE'):
                    self.__score = long(line[7:])

        self.__attrs_c = attrs
        self.__kwds_c = kwds

    def get_attrs(self):
        if self.__attrs_c is None:
            self._parse_attrs()
        return self.__attrs_c
    attrs = property(get_attrs)

    def get_keywords(self):
        if self.__kwds_c is None:
            self._parse_attrs()
        return self.__kwds_c
    keywords = property(get_keywords)

    def get_score(self):
        if self.__score is None:
            self._parse_attrs()
        return self.__score
    score = property(get_score)

    def get_snippet(self):
        return self.__snippet
    snippet = property(get_snippet)

    def highlight(self):
        for s in self.snippet:
            tmp = s.split('\t', 1)
            if len(tmp) == 2:
                yield (unicode(tmp[0], 'utf-8'), 1)
            else:
                yield (unicode(tmp[0], 'utf-8'), 0)


class MetaData(object):
    """
    The object which represents Hyper Estaier result's meta data,
    the total number of result documents, registered documents, etc.
    """

    def __init__(self, lines):
        self.__lines = lines
        self.__cache = None

    def _parse(self):
        META_PART = {
            'VERSION' : lambda x : x[0],
            'NODE'    : lambda x : x[0],
            'HIT'     : lambda x : int(x[0]),
            'DOCNUM'  : lambda x : int(x[0]),
            'WORDNUM' : lambda x : int(x[0]),
            'TIME'    : lambda x : float(x[0]),
            'VIEW'    : lambda x : x[0],
        }
        metadata = {}
        for line in self.__lines:
            tokens = line.split('\t')
            if len(tokens) > 1:
                metadata[tokens[0]] = META_PART.get(tokens[0], lambda x: x)(tokens[1:])
        self.__cache = metadata

    def _rawdata(self):
        return ''.join(self.__lines)
    rawdata = property(_rawdata)

    def __getitem__(self, key):
        if self.__cache is None:
            self._parse()
        return self.__cache[key]

    def keys(self):
        if self.__cache is None:
            self._parse()
        return self.__cache.keys()

    def values(self):
        if self.__cache is None:
            self._parse()
        return self.__cache.values()

    def items(self):
        if self.__cache is None:
            self._parse()
        return self.__cache.items()

    def __iter__(self):
        return iter(self.keys())

    def __len__(self):
        return len(self.keys())

def parse_draft(fp):
    draft = fp.read()
    tmp = draft.split('\n\n', 1)
    if len(tmp) != 2:
        raise ValueError('invalid draft')

    return ResultDocument(attrs=tmp[0].split('\n'), snippet=tmp[1].split('\n'))

def parse_search_result(fp):
    """
    Parse the response from a Hyper Estarier node and
    return the metadata and the result documents.
    """

    # read the first line
    # and get the boundary.
    boundary = fp.readline()
    if not boundary:
        # no boundary.
        return

    end_boundary = boundary[:-1] + ":END\n"

    # parse the search meta data.
    metadata_lines = []
    while 1:
        line = fp.readline()
        if not line:
            break
        
        if line == boundary or line == end_boundary:
            break
        else:
            metadata_lines.append(line[:-1])

    metadata = MetaData(metadata_lines)

    # parse the snippets.
    end = False
    results = []
    while 1:
        snippet = []
        attributes = []
        while 1:
            line = fp.readline()
            if not line:
                end = True
                break
            
            if line == boundary:
                break
            elif line == end_boundary:
                end = True
                break
            
            line = line[:-1]
            if not line:
                break
            else:
                attributes.append(line)

        if end:
            break

        while 1:
            line = fp.readline()
            if line == boundary or line == end_boundary:
                break
            
            line = line[:-1]
            if line:
                snippet.append(line)

        results.append(ResultDocument(snippet=snippet, attrs=attributes))

    return metadata, results


def lazy_property(key, doc=None):
    def get(self):
        if self._metadata is None:
            self._fill()
        return self._metadata[key]
    return property(get, doc=doc)


class ResultSet(object):
    def __init__(self, node, query, max, low=0, high=None, step=1):
        self.node = node
        self.query = query
        self.__max = max
        self.__low = low
        self.__high = high or max
        self.__step = step

        self._metadata = None
        self._items = None

    def _clone(self, low, high, step):
        results = ResultSet(self.node, self.query.copy(), high - low, low, high, step)

        if self._items is not None:
            if low >= self.__low and high <= self.__high:
                results._items = self._items[low:high]
                results._metadata = self._metadata

        return results

    def _fill(self):
        self.query['skip'] = self.__low
        self.query['max']  = self.__high - self.__low

        print self.query

        res = self.node.send_request('search', self.query)
        self._metadata, self._items = parse_search_result(res.fp)
        res.close()

    def __len__(self):
        if self.__max % self.__step == 0:
            return self.__max / self.__step
        else:
            return int(self.__max / floor(self.__step)) + 1

    def __getitem__(self, key):
        if isinstance(key, slice):
            return self._clone(*key.indices(self.__max))
        elif isinstance(key, int):
            if key >= self.__max:
                raise IndexError('list index out of range')
            elif key < 0:
                key = self.__max + key
                if key < 0:
                    raise IndexError('list index out of range')

            if self._items is None:
                self._fill()

            return self._items[key]
        else:
            raise IndexError('list indices must be integers')

    def __iter__(self):
        if self._items is None:
            self._fill()

        return iter(self._items[::self.__step])

    # --------------------------------
    # results properties

    hit = lazy_property('HIT', 'the number of documents which matche the given query.')

    version = lazy_property('VERSION', 'the version of Hyper Esraier node protocol.')

    time = lazy_property('TIME', 'the total time(seconds) to retrieve the results.')

    docnum = lazy_property('DOCNUM', 'the number of all the registered documents.')

    wordnum = lazy_property('WORDNUM', 'the number of all the registered words')

    def get_links(self):
        raise NotImplementedError
    links = property(get_links)

    def get_hints(self):
        raise NotImplementedError
    hints = property(get_hints)

    def get_times(self):
        raise NotImplementedError
    times = property(get_times)


if __name__ == '__main__':
    pass

